/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package mwt.xml.xdbforms.xformlayer.transactions.impl;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import mwt.xml.xdbforms.cache.CacheService;
import mwt.xml.xdbforms.cache.CacheServiceFactory;
import mwt.xml.xdbforms.cache.exception.CacheServiceException;
import mwt.xml.xdbforms.dblayer.ConnectionFactory;
import mwt.xml.xdbforms.dblayer.query.Criteria;
import mwt.xml.xdbforms.dblayer.query.Criterion;
import mwt.xml.xdbforms.dblayer.query.Query;
import mwt.xml.xdbforms.dblayer.query.SQLOperation;
import mwt.xml.xdbforms.dblayer.query.exception.QueryException;
import mwt.xml.xdbforms.dblayer.query.impl.CriteriaImpl;
import mwt.xml.xdbforms.dblayer.query.impl.Restriction;
import mwt.xml.xdbforms.dblayer.query.impl.SQLQuery;
import mwt.xml.xdbforms.schemalayer.SchemaDocument;
import mwt.xml.xdbforms.schemalayer.SchemaDocumentBuilder;
import mwt.xml.xdbforms.schemalayer.SchemaDocumentBuilderFactory;
import mwt.xml.xdbforms.schemalayer.exception.SchemaBuildingException;
import mwt.xml.xdbforms.schemalayer.exception.SchemaDocumentBuilderException;
import mwt.xml.xdbforms.xformlayer.XFormDocument;
import mwt.xml.xdbforms.xformlayer.XFormDocumentBuilder;
import mwt.xml.xdbforms.xformlayer.XFormDocumentBuilderFactory;
import mwt.xml.xdbforms.xformlayer.exception.XFormDocumentException;
import mwt.xml.xdbforms.xformlayer.transactions.CommitterService;
import mwt.xml.xdbforms.xformlayer.transactions.XFormTransaction;
import mwt.xml.xdbforms.xformlayer.transactions.exception.CommitterServiceCommitEx;
import mwt.xml.xdbforms.xformlayer.transactions.exception.CommitterServiceValidateEx;
import mwt.xml.xdbforms.xformlayer.transactions.exception.XFormTransactionException;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.ls.DOMImplementationLS;
import org.w3c.dom.ls.LSOutput;
import org.w3c.dom.ls.LSSerializer;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 * Progetto Master Web Technology
 * @author Gianfranco Murador, Cristian Castiglia, Matteo Ferri
 *
 *
 */
public class XFormUpdate implements XFormTransaction {

    // private ConnectionFactory cf;
    private String database;
    private SchemaDocumentBuilderFactory schemaFactory;
    private XFormDocumentBuilderFactory xformFactory;
    private CacheService xformCache;
    private CacheService schemaCache;
    private CacheService dataInstanceCache;
    private String table;
    private XFormDocument xfd;
    private SchemaDocument sd;
    private Map<String, String> parameters;
    private Object[] keys;

    public XFormUpdate(String database) {
        this.database = database;
        schemaFactory = SchemaDocumentBuilderFactory.newInstance();
        xformFactory = XFormDocumentBuilderFactory.newInstance();
        CacheServiceFactory csf = CacheServiceFactory.newInstance();
        xformCache = csf.newXFormCacheService();
        schemaCache = csf.newXSchemaCacheService();
        dataInstanceCache = csf.newDataInstanceCacheService();

    }

    public void require(String table, Submission s, Object[] keys) throws XFormTransactionException {

        SchemaDocumentBuilder schemaBuilder = schemaFactory.newSchemaDocumentBuilder();
        XFormDocumentBuilder xformBuilder = xformFactory.newXFormDocumentBuilder();
        ConnectionFactory cf = ConnectionFactory.getInstance();
        cf.setDatabase(database);

        this.table = table;
        this.keys = keys;

        parameters = new HashMap<String, String>();
        parameters.putAll(s.toHashMap());

        Map<String, String> nsBindings = new HashMap<String, String>();
        nsBindings.put("xs", XMLConstants.W3C_XML_SCHEMA_INSTANCE_NS_URI);
        try {
            schemaBuilder.addNameSpaceBinding(nsBindings);

            sd = schemaBuilder.newSchemaDocument(table, database);
            xfd = xformBuilder.newXFormDocument();
            Query query = cf.getQuery();

            SQLOperation operation = new SQLQuery(table);
            Criteria criteria = new CriteriaImpl();

            String pkColname[] = sd.getAllPrimaryKeysNames();

            if (pkColname.length != keys.length || pkColname.length == 0) {

                throw new XFormTransactionException("Error, key values length mismatch");

            }

            /**
             * Modificare
             * Se le key e' == 1,
             * allora
             **/
            Criterion criterions[] = new Criterion[pkColname.length];


            if (criterions.length == 1) {
                criterions[0] = Restriction.eq(pkColname[0], keys[0]);
                criteria.addCriterion(criterions[0]);
            } else {


                for (int i = 0; i < keys.length; i++) {
                    Object object = keys[i];
                    criterions[i] = Restriction.eq(pkColname[i], object);

                }
                Criterion and = Restriction.and(criterions[0], criterions[1]);

                if (criterions.length > 2) {

                    for (int i = 2; i < criterions.length; i++) {

                        and = Restriction.and(and, criterions[i]);
                    }
                }
                criteria.addCriterion(and);

            }



            operation.addCriteria(criteria);
            Collection values = query.read(operation);
            Iterator it = values.iterator();
            List row = (List) it.next();
            System.out.println("Grandezza collection " + values.size());
            xfd.buildDataModel(sd, s.getSchemaURL().toString(), row);


        } catch (QueryException ex) {
            throw new XFormTransactionException(ex.getMessage(), ex);
        } catch (XFormDocumentException ex) {
            throw new XFormTransactionException(ex.getMessage(), ex);
        } catch (SchemaDocumentBuilderException ex) {
            throw new XFormTransactionException(ex.getMessage(), ex);
        }


    }

    public void require(String table, Submission s) throws XFormTransactionException {

        require(table, s, new Object[0]);

    }

    /**
     * Definire un validatore dell'input
     * @param inputStream
     * @throws mwt.xml.xdbforms.xformlayer.transactions.exception.XFormTransactionException
     */
    public void commit(InputStream inputStream) throws XFormTransactionException {
        try {
            CommitterService cs = new CommitterServiceImpl();
            cs.validate(sd, inputStream);
            cs.update(table, keys);
        } catch (CommitterServiceCommitEx ex) {
            throw new XFormTransactionException(ex.getMessage(), ex);
        } catch (CommitterServiceValidateEx ex) {
            throw new XFormTransactionException(ex.getMessage(), ex);
        }


    }

    public String getDocument(OutputStream os) throws XFormTransactionException {

        ByteArrayOutputStream baos;
        try {

            if (xformCache.contains(database + "_" + table + "_update")) {

                baos = (ByteArrayOutputStream) xformCache.getLastObject(database + "_" + table + "_update");

            } else {
                baos = new ByteArrayOutputStream();

                xfd.buildPresentation(parameters, baos);
                xformCache.load(baos, database + "_" + table + "_update");
               // baos.writeTo(os);
            }

            System.out.println("getDocument()\n***********\n" + baos.toString() + "\n**********************");

            return baos.toString();
       /* } catch (IOException ex) {
            throw new XFormTransactionException(ex.getMessage(), ex);
        */} catch (CacheServiceException ex) {
            throw new XFormTransactionException(ex.getMessage(), ex);
        } catch (XFormDocumentException ex) {
            throw new XFormTransactionException("getDocument(): " + ex.getMessage(), ex);
        }
    }

    public String getSchemasBinding(OutputStream os) throws XFormTransactionException {
        try {
            Document document;

            if (schemaCache.contains(database + "_" + table + "_update")) {

                document = (Document) schemaCache.getLastObject(database + "_" + table + "_update");

            } else {
                document = sd.getSchemaDocument();
                schemaCache.load(document, database + "_" + table + "_update");
            }
            DOMImplementationLS domimpl = (DOMImplementationLS) document.getImplementation();
            LSOutput lso = domimpl.createLSOutput();
            LSSerializer lss = domimpl.createLSSerializer();
            lso.setEncoding("UTF-8");
            Writer w = new OutputStreamWriter(os);
            lso.setCharacterStream(w);
            lss.getDomConfig().setParameter("format-pretty-print", true);
            lss.write(document, lso);

            System.out.println("SCHEMA -----\n "+toString(document)+"\n-----");

            return toString(document);
        } catch (CacheServiceException ex) {
            throw new XFormTransactionException(ex.getMessage(), ex);
        } catch (SchemaBuildingException ex) {
            throw new XFormTransactionException("getSchemasBinding " + ex.getMessage(), ex);
        }

    }

    public String getDataInstance(OutputStream os) throws XFormTransactionException {

        try {


            Document document;
            /*if (dataInstanceCache.contains(database + "_" + table + "_update")) {
            document = (Document) dataInstanceCache.getLastObject(database + "_" + table + "_update");
            } else {*/

            document = xfd.getDataModel();
            //     dataInstanceCache.load(document, database + "_" + table + "_update");

            //}


            DOMImplementationLS domimpl = (DOMImplementationLS) document.getImplementation();
            LSOutput lso = domimpl.createLSOutput();
            LSSerializer lss = domimpl.createLSSerializer();
            lso.setEncoding("UTF-8");
            Writer w = new OutputStreamWriter(os);
            lso.setCharacterStream(w);
            lss.getDomConfig().setParameter("format-pretty-print", true);
            lss.write(document, lso);

            return toString(document);
        /*  } catch (CacheServiceException ex) {
        throw new XFormTransactionException(ex.getMessage(), ex);*/
        } catch (XFormDocumentException ex) {
            throw new XFormTransactionException("getDataInstance() " + ex.getMessage(), ex);
        }
    }

    public String getValuesInstance(OutputStream os) throws XFormTransactionException {
        try {
            Document document = null;
            if (dataInstanceCache.contains(database + "_" + table + "_update_val")) {
                document = (Document) dataInstanceCache.getLastObject(database + "_" + table + "_update_val");
            } else {
                if (xfd.getValueModel() != null) {
                    document = xfd.getValueModel();
                    dataInstanceCache.load(document, database + "_" + table + "_update_val");
                }
            }

            if (document != null) {
                DOMImplementationLS domimpl = (DOMImplementationLS) document.getImplementation();
                LSOutput lso = domimpl.createLSOutput();
                LSSerializer lss = domimpl.createLSSerializer();
                lso.setEncoding("UTF-8");
                Writer w = new OutputStreamWriter(os);
                lso.setCharacterStream(w);
                lss.getDomConfig().setParameter("format-pretty-print", true);
                lss.write(document, lso);
                return toString(document);
            }

            return null;

        } catch (XFormDocumentException ex) {
            throw new XFormTransactionException(ex.getMessage(), ex);
        } catch (CacheServiceException ex) {
            throw new XFormTransactionException(ex.getMessage(), ex);
        }

    }

    public String getMergedDocument() throws XFormTransactionException {

        Document xform = null;


        try {

            Document dataModel = xfd.getDataModel();
            Document valuesModel = xfd.getValueModel();
            ByteArrayOutputStream baos = new ByteArrayOutputStream();

            String docStr = getDocument(null);
            DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
            dbf.setValidating(false);
            dbf.setNamespaceAware(true);
            InputSource inputSource = new InputSource();
            inputSource.setCharacterStream(new StringReader(docStr));
            DocumentBuilder builder = dbf.newDocumentBuilder();
            xform = builder.parse(inputSource);

            NodeList nl = xform.getElementsByTagName("xf:instance");

            for (int i = 0; i < nl.getLength(); i++) {
                nl.item(i).getAttributes().removeNamedItem("src");
              //  Element e = (Element) nl.item(i);
            }

            Node n = nl.item(0);
            Node impNode = xform.importNode(valuesModel.getDocumentElement(), true);
            n.appendChild(impNode);
            n = nl.item(1);
            impNode = xform.importNode(dataModel.getDocumentElement(), true);
            n.appendChild(impNode);

            DOMImplementationLS domimpl = (DOMImplementationLS) xform.getImplementation();
            LSOutput lso = domimpl.createLSOutput();
            LSSerializer lss = domimpl.createLSSerializer();
          //  lso.setEncoding("UTF-8");
            Writer w = new OutputStreamWriter(baos);
            lso.setCharacterStream(w);
            lss.getDomConfig().setParameter("format-pretty-print", true);
            lss.write(xform, lso);

            return baos.toString();

        // Riprendo il document xform
        } catch (SAXException ex) {
            throw new XFormTransactionException(ex.getMessage(), ex);
        } catch (IOException ex) {
            throw new XFormTransactionException(ex.getMessage(), ex);
        } catch (ParserConfigurationException ex) {
            throw new XFormTransactionException(ex.getMessage(), ex);
        } catch (XFormDocumentException ex) {
            throw new XFormTransactionException(ex.getMessage(), ex);
        } catch (Exception e) {
            throw new XFormTransactionException(e.getMessage(), e);
        }
    }

    private String toString(Document d) {


        try {
            DOMSource domSource = new DOMSource(d);
            StringWriter writer = new StringWriter();
            StreamResult result = new StreamResult(writer);
            TransformerFactory tf = TransformerFactory.newInstance();
            Transformer transformer = tf.newTransformer();
            transformer.transform(domSource, result);
            return writer.toString();
        } catch (TransformerException ex) {
            ex.printStackTrace();
            return null;
        }

    }
}
